package top.lingkang.mm.orm;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.NamingCase;
import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.session.Configuration;
import top.lingkang.mm.MagicConfiguration;
import top.lingkang.mm.annotation.*;
import top.lingkang.mm.constant.IdType;
import top.lingkang.mm.error.MagicException;
import top.lingkang.mm.gen.DefaultIdGenerate;
import top.lingkang.mm.gen.IdGenerate;
import top.lingkang.mm.utils.MagicUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.*;

/**
 * @author lingkang
 * @create by 2024/3/12 10:34
 */
class MagicEntityUtils {
    private static final Map<Class<?>, MagicEntity> cache = new HashMap<>();
    private static final Map<Class<?>, SetTime> setTime = new HashMap<>();

    static {
        setTime.put(Date.class, new SetTimeDate());
        setTime.put(java.sql.Date.class, new SetTimeDateSql());
        setTime.put(Long.class, new SetTimeLong());
        setTime.put(String.class, new SetTimeString());
        setTime.put(Timestamp.class, new SetTimeTimestamp());
        setTime.put(long.class, new SetTimeLong());
    }

    /**
     * 对entity类进行解析
     *
     * @param clazz 实体类
     * @return MagicEntity
     */
    public static MagicEntity getMagicEntity(Class<?> clazz) {
        MagicEntity entity = cache.get(clazz);
        if (entity != null)
            return entity;
        // 检查
        Table table = clazz.getAnnotation(Table.class);
        Assert.notNull(table, "实体类 " + clazz.getName() + " 没有 @Table 注解");

        entity = new MagicEntity();
        entity.setClazz(clazz);
        // 设置表名
        entity.setTableName(StrUtil.isNotEmpty(table.value()) ? table.value() : clazz.getSimpleName());

        // 事件
        for (Method method : MagicUtils.getAllPublicMethod(clazz)) {
            if (method.isAnnotationPresent(PostUpdate.class)) {
                if (entity.getPostUpdate() == null)
                    entity.setPostUpdate(new ArrayList<>());
                if (!Modifier.isPublic(method.getModifiers()))
                    method.setAccessible(true);
                entity.getPostUpdate().add(method);
            }
            if (method.isAnnotationPresent(PreUpdate.class)) {
                if (entity.getPreUpdate() == null)
                    entity.setPreUpdate(new ArrayList<>());
                if (!Modifier.isPublic(method.getModifiers()))
                    method.setAccessible(true);
                entity.getPreUpdate().add(method);
            }
        }

        // 设置列
        int i = 0;
        for (Field field : MagicUtils.getAllField(clazz)) {
            if (field.isAnnotationPresent(IgnoreColumn.class))
                continue;
            Id id = field.getAnnotation(Id.class);
            if (id != null) {
                if (entity.getIdAnn() != null)
                    throw new MagicException("已经存在重复 @Id 注解：" + entity.getIdAnn().annotationType().getName());
                if (id.value() == null)
                    throw new MagicException("@Id 注解的类型不能为空：" + entity.getClazz().getName() + " 属性字段：" + field.getName());
                entity.setIdAnn(field.getAnnotation(Id.class));
                entity.setIdIndex(i);
            }
            // 非公共设置访问
            if (!Modifier.isPublic(field.getModifiers()))
                field.setAccessible(true);
            entity.getFields().add(field);
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                if (StrUtil.isNotEmpty(column.value()))
                    entity.getColumnName().add(column.value());
                if (column.toUnderlineCase())// 驼峰转换 v1.1.0+
                    entity.getColumnName().add(NamingCase.toUnderlineCase(field.getName()));
            } else
                entity.getColumnName().add(field.getName());
            i++;

            // 设置自动生成时间 v1.1.0+
            AutoCreateTime autoCreateTime = field.getAnnotation(AutoCreateTime.class);
            if (autoCreateTime != null) {
                if (field.getAnnotation(AutoUpdateTime.class) != null)
                    throw new MagicException("@AutoCreateTime 和 @AutoUpdateTime 注解不能作用于同一属性：" + entity.getClazz().getName() + " 属性字段：" + field.getName());
                if (!setTime.containsKey(field.getType()))
                    throw new MagicException(entity.getClazz().getName() + " 属性字段：" + field.getName() + ": @AutoCreateTime 不支持的属性类型：" + field.getType().getName());
                if (entity.getAutoCreateTime() == null)
                    entity.setAutoCreateTime(new ArrayList<>());
                entity.getAutoCreateTime().add(field);
            }
            AutoUpdateTime autoUpdateTime = field.getAnnotation(AutoUpdateTime.class);
            if (autoUpdateTime != null) {
                if (field.getAnnotation(AutoCreateTime.class) != null)
                    throw new MagicException("@AutoCreateTime 和 @AutoUpdateTime 注解不能作用于同一属性：" + entity.getClazz().getName() + " 属性字段：" + field.getName());
                if (!setTime.containsKey(field.getType()))
                    throw new MagicException(entity.getClazz().getName() + " 属性字段：" + field.getName() + ": @AutoUpdateTime 不支持的属性类型：" + field.getType().getName());
                if (entity.getAutoUpdateTime() == null) {
                    entity.setAutoUpdateTime(new ArrayList<>());
                    entity.setAutoUpdateTimeColumn(new ArrayList<>());
                }
                entity.getAutoUpdateTime().add(field);
                entity.getAutoUpdateTimeColumn().add(entity.getColumnName().get(entity.getColumnName().size() - 1));
            }
        }
        Assert.isFalse(entity.getColumnName().isEmpty(), "实体映射没有查询列属性: " + clazz.getName());
        cache.put(clazz, entity);
        String columns_ = MagicEntityUtils.getColumns(entity.getColumnName(), null);
        entity.setSelectTableSql("select " + columns_ + " from " + entity.getTableName() + " ");
        return entity;
    }

    public static String getColumns(List<String> col, String prefix) {
        StringBuilder res = new StringBuilder();
        if ("".equals(prefix))
            prefix = null;
        for (int i = 0; i < col.size(); i++) {
            if (prefix != null)
                res.append(prefix).append(".").append(col.get(i));
            else
                res.append(col.get(i));
            if (i < col.size() - 1)
                res.append(", ");
        }
        return res.toString();
    }

    public static String getInsertValues(MagicEntity entity, String prefix) {
        List<String> col = entity.getColumnName();
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < col.size(); i++) {
            if (prefix != null)
                res.append("#{").append(prefix).append(".").append(col.get(i)).append("}");
            else
                res.append("#{").append(col.get(i)).append("}");
            if (i < col.size() - 1)
                res.append(",");
        }
        return res.toString();
    }

    public static void setIdValue(Object obj, Configuration configuration, MagicEntity entity) throws IllegalAccessException {
        if (entity.getIdAnn().value() == IdType.ASSIGN) {
            Field field = entity.getFields().get(entity.getIdIndex());
            if (field.get(obj) != null)
                return;
            IdGenerate idGenerate = DefaultIdGenerate.instance;
            if (configuration instanceof MagicConfiguration) {
                idGenerate = ((MagicConfiguration) configuration).getIdGenerate();
            }

            Object id = idGenerate.nextId(entity.getIdAnn().assignParam());
            if (field.getType() != id.getClass())
                throw new MagicException("id: IdType.ASSIGN。实体" + entity.getClazz().getName() + "的 " + field.getName() +
                        " 属性类型：" + field.getType().getName() + "  idGenerate 自动生成的id类型: " +
                        id.getClass().getName() + " 类型不一致导致自动生成id失败，请检查 idGenerate 返回类型与实体id类型是否一致");
            else
                field.set(obj, id);
        }
    }

    public static Map<String, Object> getInsertNotNullParams(Object obj, MagicEntity entity) {
        Map<String, Object> param = new HashMap<>();
        StringBuilder columns = new StringBuilder();
        StringBuilder values = new StringBuilder();
        boolean isSequence = false;
        if (entity.getIdAnn() != null && entity.getIdAnn().value() == IdType.AUTO && StrUtil.isNotEmpty(entity.getIdAnn().sequence())) {
            isSequence = true;
        }
        for (int i = 0; i < entity.getColumnName().size(); i++) {
            try {
                Field field = entity.getFields().get(i);
                Object value = field.get(obj);
                // sequence
                if (isSequence && value == null) {
                    if (entity.getColumnName().size() == 1)
                        throw new MagicException("插入数据失败，插入的表列必须大于1: " + obj);
                    columns.append(entity.getColumnName().get(i)).append(", ");
                    values.append("nextval('").append(entity.getIdAnn().sequence()).append("'), ");
                    continue;
                }
                if (value != null) {
                    columns.append(entity.getColumnName().get(i)).append(", ");
                    values.append("#{e.").append(entity.getFields().get(i).getName()).append("}, ");
                }
            } catch (Exception e) {
                throw new MagicException(e);
            }
        }
        if (columns.length() == 0)
            throw new MagicException("不能插入全空值：" + obj);
        param.put("columns", columns.substring(0, columns.length() - 2));
        param.put("values", values.substring(0, values.length() - 2));
        param.put("e", obj);
        return param;
    }

    /**
     * @param cols ["a","b","c"]
     * @return "a, b, c"
     */
    public static String getOrderBy(String[] cols) {
        StringBuilder sql = new StringBuilder();
        for (int i = 0; i < cols.length; i++) {
            sql.append(cols[i]);
            if (i < cols.length - 1)
                sql.append(", ");
        }
        return sql.toString();
    }


    /**
     * @param cols ["a","b","c"]
     * @param by   desc
     * @return "a desc, b desc, c desc"
     */
    public static String getOrderBy(String[] cols, String by) {
        StringBuilder sql = new StringBuilder();
        for (int i = 0; i < cols.length; i++) {
            sql.append(cols[i]).append(" ").append(by);
            if (i < cols.length - 1)
                sql.append(", ");
        }
        return sql.toString();
    }

    public static void execPreUpdate(MagicEntity entity, Object obj) {
        if (entity.getPreUpdate() == null)
            return;
        for (Method method : entity.getPreUpdate()) {
            try {
                method.invoke(obj);
            } catch (Exception e) {
                throw new MagicException("调用 @PreUpdate 异常: " + entity.getClazz().getName(), e);
            }
        }
    }

    public static void execPostUpdate(MagicEntity entity, Object obj) {
        if (entity.getPostUpdate() == null)
            return;
        for (Method method : entity.getPostUpdate()) {
            try {
                method.invoke(obj);
            } catch (Exception e) {
                throw new MagicException("调用 @PostUpdate 异常: " + entity.getClazz().getName(), e);
            }
        }
    }

    /**
     * id=#{prefix.id}, username=#{prefix.username}
     *
     * @param entity user
     * @param prefix e
     * @return
     */
    public static String getSetColumns(MagicEntity entity, String prefix) {
        if ("".equals(prefix))
            prefix = null;
        List<String> col = entity.getColumnName();
        List<Field> fields = entity.getFields();
        StringBuilder set = new StringBuilder();
        for (int i = 0; i < col.size(); i++) {
            if (prefix != null)
                set.append(col.get(i)).append("=#{").append(prefix).append(".").append(fields.get(i).getName()).append("}");
            else
                set.append(col.get(i)).append("=#{").append(fields.get(i).getName()).append("}");
            if (i < col.size() - 1)
                set.append(", ");
        }
        return set.toString();
    }

    public static String getInsertPrefixValues(MagicEntity entity, String prefix) {
        if ("".equals(prefix))
            prefix = null;
        boolean isSequence = false;
        if (entity.getIdAnn() != null && entity.getIdAnn().value() == IdType.AUTO && StrUtil.isNotEmpty(entity.getIdAnn().sequence())) {
            isSequence = true;
        }
        List<String> col = entity.getColumnName();
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < col.size(); i++) {
            if (isSequence && i == entity.getIdIndex()) {
                res.append("nextval('").append(entity.getIdAnn().sequence()).append("')");
            } else {
                if (prefix != null)
                    res.append("#{").append(prefix).append(".").append(col.get(i)).append("}");
                else
                    res.append("#{").append(col.get(i)).append("}");
            }
            if (i < col.size() - 1)
                res.append(",");
        }
        return res.toString();
    }

    public static void execPreUpdateList(MagicEntity entity, List list) {
        if (entity.getPreUpdate() == null)
            return;
        for (Method method : entity.getPreUpdate()) {
            try {
                for (Object o : list)
                    method.invoke(o);
            } catch (Exception e) {
                throw new MagicException("调用 @PreUpdate 异常: " + entity.getClazz().getName(), e);
            }
        }
    }

    public static void execPostUpdateList(MagicEntity entity, List list) {
        if (entity.getPostUpdate() == null)
            return;
        for (Method method : entity.getPostUpdate()) {
            try {
                for (Object o : list)
                    method.invoke(o);
            } catch (Exception e) {
                throw new MagicException("调用 @PostUpdate 异常: " + entity.getClazz().getName(), e);
            }
        }
    }

    public static void autoSetTimeList(MagicEntity entity, List list, boolean create) {
        for (Object o : list) {
            autoSetTime(entity, o, create);
        }
    }

    public static void autoSetTime(MagicEntity entity, Object obj, boolean create) {
        if (create && entity.getAutoCreateTime() != null) {
            for (Field field : entity.getAutoCreateTime()) {
                try {
                    if (field.get(obj) != null)
                        continue;
                    setTime.get(field.getType()).set(field, obj);
                } catch (IllegalAccessException e) {
                    throw new MagicException("@AutoCreateTime 设置值异常: " + entity.getClazz().getName() + " " + field.getName(), e);
                }
            }
        }
        if (entity.getAutoUpdateTime() != null) {
            for (Field field : entity.getAutoUpdateTime()) {
                try {
                    setTime.get(field.getType()).set(field, obj);
                } catch (IllegalAccessException e) {
                    throw new MagicException("@AutoUpdateTime 设置值异常: " + entity.getClazz().getName() + " " + field.getName(), e);
                }
            }
        }
    }
}
